package com.linking.web.repository;

import com.linking.web.common.SwitchConstants;
import com.linking.web.manager.redis.RedisSelfUtils;
import com.linking.web.pojo.po.BasePO;
import com.linking.web.pojo.po.BasePO.RefreshType;
import com.linking.web.pojo.po.EmptyPO;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.springframework.stereotype.Repository;

/**
 * 单个数据的仓库基类
 *
 * @author yaoweixin
 * @date 2019/2/13
 */
@Repository
public abstract class AbstractSingleRepository<T extends BasePO> extends BaseRepository {

  @Resource
  RedisSelfUtils redisUtils;

  /**
   * 获得数据库名
   *
   * @return 数据库名
   */
  protected abstract String getDbName();

  /**
   * 获得数据库集合名
   *
   * @return 数据库集合名
   */
  protected abstract String getCollection();

  /**
   * 是否需要保存
   *
   * @return true|false
   */
  protected abstract Boolean isCache();

  /**
   * 是否需要在缓存设置
   *
   * @return true|false
   */
  protected abstract Boolean isSetCache();

  /**
   * 是否需要在缓存读取
   *
   * @return true|false
   */
  protected abstract Boolean isGetCache();

  /**
   * 根据主键编号从数据库查询对象
   *
   * @param id 主键编号
   * @return 结果
   */
  protected abstract T queryByPrimary(String id);

  /**
   * 在从数据库查询出对象后
   *
   * @param t 范型对象
   */
  protected abstract void afterQuery(T t);

  /**
   * DB层插入对象
   *
   * @param t 范型对象
   */
  protected abstract void insert(T t);

  /**
   * DB层修改对象
   *
   * @param t 范型对象
   */
  protected abstract void update(T t);

  /**
   * DB层移除对象
   *
   * @param t 范型对象
   */
  protected abstract void delete(T t);

  /**
   * 获得对象的redis KEY
   *
   * @param id 主键编号
   * @return 结果
   */
  protected abstract String getRedisKey(String id);

  /**
   * 超时时间
   *
   * @return 结果
   */
  protected abstract Long expireTime();

  /**
   * 获得对象编号
   *
   * @param t 范型对象
   * @return 结果
   */
  protected abstract String getId(T t);

  /**
   * 在仓库刷新对象后
   *
   * @param refreshType 刷新类型
   * @param t           范型对象
   */
  protected abstract void afterRefresh(RefreshType refreshType, T t);

  private boolean cacheOpen() {
    return SwitchConstants.isCacheOpen();
  }

  /**
   * 对象保存进缓存
   *
   * @param t 对象
   */
  private void toCache(T t) {
    if (cacheOpen() && isSetCache()) {
      String key = getRedisKey(getId(t));
      redisUtils.set(key, t, expireTime(), TimeUnit.SECONDS);
    }
  }

  /**
   * 从缓存中获取对象
   *
   * @param id 主键编号
   * @return 对象
   */
  @SuppressWarnings("unchecked")
  private T fromCache(String id) {
    if (cacheOpen() && isGetCache()) {
      String key = getRedisKey(id);
      Object obj = redisUtils.get(key);
      return (T) obj;
    } else {
      return null;
    }
  }

  /**
   * 从缓存中删除对象
   *
   * @param t 对象
   */
  private void removeCache(T t) {
    if (cacheOpen() && isSetCache()) {
      String key = getRedisKey(getId(t));
      redisUtils.remove(key);
    }
  }

  /**
   * 判断是否是空对象
   *
   * @param t 对象
   * @return true|false
   */
  private Boolean isEmptyCache(T t) {
    return t instanceof EmptyPO;
  }

  /**
   * 设置临时缓存，防止缓存穿透
   *
   * @param id 主键编号
   */
  private void setEmptyCache(String id) {
    if (cacheOpen() && isSetCache()) {
      // 临时存入缓存，防止缓存穿透
      String key = getRedisKey(id);
      redisUtils.set(key, new EmptyPO(), 60L, TimeUnit.SECONDS);
    }
  }

  /**
   * 获取对象(只从缓存取，没有则返回空)
   *
   * @param id 主键编号
   * @return 对象
   */
  public Optional<T> get(String id) {
    if (cacheOpen() && isGetCache()) {
      T t = fromCache(id);
      if (isEmptyCache(t)) {
        return Optional.empty();
      }
      return Optional.ofNullable(t);
    } else {
      return Optional.empty();
    }
  }

  /**
   * 获取对象(先从缓存取，没有从数据库中查)
   *
   * @param id 主键编号
   * @return 对象
   */
  public Optional<T> getAlways(String id) {
    T t = fromCache(id);
    if (isEmptyCache(t)) {
      return Optional.empty();
    }
    if (t == null) {
      t = queryByPrimary(id);
      if (t != null) {
        afterQuery(t);
      } else {
        // 设置临时缓存，防止缓存穿透
        setEmptyCache(id);
      }
    }
    return Optional.ofNullable(t);
  }

  /**
   * 对象刷新至仓库
   *
   * @param t 泛型对象
   */
  public void refresh(T t) {
    RefreshType refreshType = t.refreshType();
    switch (refreshType) {
      case Create:
        insert(t);
        break;
      case Change:
        t.setUpdateTime(t.getCurTime());
        update(t);
        break;
      case Delete:
        delete(t);
        break;
      default:
        break;
    }

    boolean needCache = t.needCache();
    boolean needClearCache = t.needClearCache();
    t.setCreate(false);
    t.setChange(false);
    t.setDelete(false);
    t.setCache(false);
    if (needCache) {
      toCache(t);
    }
    if (needClearCache) {
      removeCache(t);
    }
    afterRefresh(refreshType, t);
  }
}
